#ifndef _transform_backward_hpp_
#define _transform_backward_hpp_

/** \file transform_backward.hpp
 *  \author Adrian Schweizer
 *  \created  $Do 23 Aug 10:32:12 pm CEST 2007 schwadri@SchwadriComp.local$
 *  \modified $Do 30 Aug 07:35:12 pm CEST 2007 schwadri@SchwadriComp.local$
 */

#include "vector_traits.hpp"

namespace core {

    namespace math {

        namespace detail {

            template<class V, class VR, class F, std::size_t n>
                struct transform_backward_unroll_loop
                {
                    static void eval(const V& v1, const V& v2, VR& v3, F binary_op)
                    {
                        v3[n]   =   binary_op(v1[n],v2[n]);
                        transform_backward_unroll_loop<V,VR,F,n-1>::eval(v1,v2,v3,binary_op);
                    }

                    static void eval(const V& v1, VR& v2, F unary_op)
                    {
                        v2[n]   =   unary_op(v1[n]);
                        transform_backward_unroll_loop<V,VR,F,n-1>::eval(v1,v2,unary_op);
                    }
                };

            // break condition
            template<class V, class VR, class F>
                struct transform_backward_unroll_loop<V,VR,F,0>
                {
                    static void eval(const V& v1, const V& v2, VR& v3, F binary_op)
                    {
                        v3[0]   =   binary_op(v1[0],v2[0]);
                    }

                    static void eval(const V& v1, VR& v2, F unary_op)
                    {
                        v2[0]   =   unary_op(v1[0]);
                    }
                };

            struct transform_backward_static_impl
            {
                template<class V, class VR, class F>
                    static void eval(const V& v1, const V& v2, VR& v3, F binary_op)
                    {
                        transform_backward_unroll_loop<V,VR,F,dimension<V>::value-1 >::eval(v1,v2,v3,binary_op);
                    };

                template<class V, class VR, class F>
                    static void eval(const V& v1, VR& v2, F unary_op)
                    {
                        transform_backward_unroll_loop<V,VR,F,dimension<V>::value-1 >::eval(v1,v2,unary_op);
                    };
            };

            struct transform_backward_dynamic_impl
            {
                template<class V, class VR, class F>
                    static void eval(const V& v1, const V& v2, VR& v3, F binary_op)
                    {
                        std::size_t dim =   dimension<V>::get(v1);
                        //check that all 3 vectors have the same dimension
                        assert( dim == dimension<V>::get(v2) &&
                                dim == dimension<V>::get(v3));
                        using std::transform;
                        transform(v1.rbegin(),v1.rend(),v2.rbegin(),v3.rbegin(),binary_op);
                    };

                template<class V, class VR, class F>
                    static void eval(const V& v1, VR& v2, F unary_op)
                    {
                        std::size_t dim =   dimension<V>::get(v1);
                        //check that all both vectors have the same dimension
                        assert( dim == dimension<V>::get(v2));
                        using std::transform;
                        transform(v1.rbegin(),v1.rend(),v2.rbegin(),unary_op);
                    };
            };

            struct transform_backward_scalar_impl
            {
                template<class V, class VR, class F>
                    static void eval(const V& v1, const V& v2, VR& v3, F binary_op)
                    {
                        v3 = binary_op(v1,v2);
                    };

                template<class V, class VR, class F>
                    static void eval(const V& v1, VR& v2, F unary_op)
                    {
                        v2 = unary_op(v1);
                    };
            };

            template<class V, class VR, class F>
                struct choose_transform_backward_impl
                {
                    //check if this is a vector with static dimension
                    typedef typename boost::mpl::eval_if<is_scalar<V>,
                                                boost::mpl::identity<transform_backward_scalar_impl>,
                                                boost::mpl::eval_if_c<(dimension<V>::value>0),
                                                                    boost::mpl::identity<transform_backward_static_impl>,
                                                                    boost::mpl::identity<transform_backward_dynamic_impl>
                                                                    >
                                                >::type  impl_type;

                    static void eval(const V& v1, const V& v2, VR& v3, F binary_op)
                    {
                        impl_type::template eval<V,VR,F>(v1,v2,v3,binary_op);
                    }

                    static void eval(const V& v1, VR& v2, F unary_op)
                    {
                        impl_type::template eval<V,VR,F>(v1,v2,unary_op);
                    }
                };



        } // namespace detail

        template<class Vector, class VectorR,  class BinaryFunctor>
            void transform_backward(const Vector& v1, const Vector& v2, VectorR& v3, BinaryFunctor& binary_op)
            {
                typedef detail::choose_transform_backward_impl<Vector,VectorR,BinaryFunctor&>   impl_type;
                impl_type::eval(v1,v2,v3,binary_op);
            };

        template<class Vector, class VectorR,  class BinaryFunctor>
            void transform_backward(const Vector& v1, const Vector& v2, VectorR& v3, const BinaryFunctor& i_binary_op)
            {
                BinaryFunctor binary_op(i_binary_op);
                typedef detail::choose_transform_backward_impl<Vector,VectorR,BinaryFunctor&>   impl_type;
                impl_type::eval(v1,v2,v3,binary_op);
            };

        template<class Vector, class VectorR,  class UnaryFunctor>
            void transform_backward(const Vector& v1, VectorR& v2, UnaryFunctor& unary_op)
            {
                typedef detail::choose_transform_backward_impl<Vector,VectorR,UnaryFunctor&>   impl_type;
                impl_type::eval(v1,v2,unary_op);
            };

        template<class Vector, class VectorR,  class UnaryFunctor>
            void transform_backward(const Vector& v1, VectorR& v2, const UnaryFunctor& i_unary_op)
            {
                UnaryFunctor unary_op(i_unary_op);
                typedef detail::choose_transform_backward_impl<Vector,VectorR,UnaryFunctor&>   impl_type;
                impl_type::eval(v1,v2,unary_op);
            };
    } // namespace core

} // namespace core

#endif // _transform_backward_hpp_
